package com.totem.base.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.totem.base.CacheHolder;
import com.totem.base.SpringContextHolder;
import com.totem.base.constants.CacheConstants;
import com.totem.base.constants.CommonConstants;
import com.totem.base.constants.ErrorCode;
import com.totem.base.exception.BaseException;
import com.totem.base.security.Authentication;
import com.totem.base.security.model.UsernamepasswordCertification;
import com.totem.base.util.FilterUtil;
import com.totem.base.util.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.Set;

@Slf4j
public class LoginFilter extends OncePerRequestFilter {
    private static final AntPathMatcher ignoresMatch = new AntPathMatcher();
    private Set<String> ignores = Set.of(
            "/admin/config/system"
    );

    private static final String adminLoginPath = "/admin/user/login";
    private static final String customerLoginPath = "/customer/login";

    private HandlerExceptionResolver resolver;
    private Authentication authentication;
    private CacheHolder cacheHolder;
    private ObjectMapper objectMapper;

    @Override
    protected void initFilterBean() throws ServletException {
        ApplicationContext ctx = SpringContextHolder.getApplicationContext();
        resolver = ctx.getBean("handlerExceptionResolver", HandlerExceptionResolver.class);
        authentication = ctx.getBean("authenticationService", Authentication.class);
        cacheHolder = ctx.getBean("cacheHolder", CacheHolder.class);
        objectMapper = ctx.getBean("objectMapper", ObjectMapper.class);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String contextPath = request.getContextPath();
        String requestUri = request.getRequestURI();
        requestUri = requestUri.substring(contextPath.length());
        if(!isAdminLogin(requestUri) && !isCustomerLogin(requestUri)){
            filterChain.doFilter(request, response);
            return;
        }

        String token = RequestUtil.resolve(request);
        if (StringUtils.isNotBlank(token)) {
            filterChain.doFilter(request, response);
            return;
        }

        String requestString = FilterUtil.getRequestString(request);
        Map loginMap = objectMapper.readValue(requestString, Map.class);
        // Map loginMap = JSONObject.parseObject(requestString, Map.class);
        log.debug("{}", loginMap);
        String loginType = String.valueOf(loginMap.get(CommonConstants.LOGIN_TYPE_KEY));
        String nickname = String.valueOf(loginMap.get(CommonConstants.LOGIN_NICKNAME_KEY));
        String password = String.valueOf(loginMap.get(CommonConstants.LOGIN_PASSWORD_KEY));
        if(StringUtils.isBlank(nickname) || StringUtils.isBlank(password)){
            resolver.resolveException(request, response, null, new BaseException(ErrorCode.UNAUTHORIZED, "nickname or password is blank"));
            return;
        }

        UsernamepasswordCertification certification = new UsernamepasswordCertification(nickname, password);
        HttpServletRequestWrapper requestWrapper = null;
        try {
            boolean result = authentication.authenticate(certification);
            if(!result) throw new BaseException(ErrorCode.UNAUTHORIZED, "certification authenticate failed");
            requestWrapper = addTokenWrapper(certification, request);
        } catch (BaseException e) {
            resolver.resolveException(request, response, null, e);
            return;
        }
        filterChain.doFilter(requestWrapper, response);
    }

    private boolean isAdminLogin(String requestPath){
        return adminLoginPath.equalsIgnoreCase(requestPath);
    }

    private boolean isCustomerLogin(String requestPath){
        return customerLoginPath.equalsIgnoreCase(requestPath);
    }

    private HttpServletRequestWrapper addTokenWrapper(UsernamepasswordCertification certification, HttpServletRequest request){
        HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(request) {
            @Override
            public String getHeader(String name){
                String headerVal = super.getHeader(name);
                if(name.equals(CacheConstants.KEY_TOKEN) && StringUtils.isBlank(headerVal)){
                    headerVal = CacheConstants.KEY_TOKEN_PREFIX + certification.getCertification();
                }
                return headerVal;
            }
        };
        return requestWrapper;
    }

    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        if(FilterUtil.ignoreMethod(request)) return true;

        final String requestUri = RequestUtil.getRelativePath(request);
        return ignores.stream().anyMatch(e->ignoresMatch.match(e, requestUri));
    }
}
